package com.adamfass.jmeutil;

import java.util.logging.Logger;

import com.jme.image.Texture;
import com.jme.input.FirstPersonHandler;
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.light.PointLight;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.state.LightState;
import com.jme.scene.state.WireframeState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.geom.Debugger;
import com.jmex.game.state.GameStateManager;
import com.jmex.game.state.StatisticsGameState;
import com.jmex.game.state.TextGameState;
import com.jmex.physics.PhysicsDebugger;
import com.jmex.physics.PhysicsSpace;

public class DebugGameState extends TextGameState {
  private static final Logger LOG = 
    Logger.getLogger(DebugGameState.class.getName());

  final PhysicsSpace physicsSpace;
  
  private final InputHandler firstPersonInputHandler;
  private final boolean enableKeys;
  
  private WireframeState wireFrameState;
  private LightState lightState;
  protected boolean statisticsCreated = false;
  private boolean pause;
  private boolean showBounds = false;
  private boolean showDepth = false;
  private boolean showNormals = false;
  private boolean showPhysics = false;
  private boolean showMarkers = false;
  
  final Node markerRootNode;

  public DebugGameState() {
    this(true, true);
  }

  public DebugGameState(boolean enableFirstPersonInput, boolean enableKeys) {
    super("");
    
    this.enableKeys = enableKeys;
    
    rootNode = new Node("RootNode");
    HackUtil.rootNode = rootNode;
    
    physicsSpace = PhysicsSpace.create();

    wireFrameState = DisplaySystem.getDisplaySystem().getRenderer()
            .createWireframeState();
    wireFrameState.setEnabled(false);
    rootNode.setRenderState(wireFrameState);

    ZBufferState zbs = DisplaySystem.getDisplaySystem().getRenderer()
            .createZBufferState();
    zbs.setEnabled(true);
    zbs.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
    rootNode.setRenderState(zbs);

    PointLight light = new PointLight();
    light.setDiffuse(new ColorRGBA(0.75f, 0.75f, 0.75f, 0.75f));
    light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
    light.setLocation(new Vector3f(100, 100, 100));
    light.setEnabled(true);

    lightState = DisplaySystem.getDisplaySystem().getRenderer()
            .createLightState();
    lightState.setEnabled(true);
    lightState.attach(light);
    rootNode.setRenderState(lightState);

    markerRootNode = new Node("MarkerRootNode");
    markerRootNode.setRenderState(lightState);
    DebugUtil.initialize(markerRootNode);
    
    if (enableFirstPersonInput) {
      firstPersonInputHandler = new FirstPersonHandler(DisplaySystem.getDisplaySystem()
              .getRenderer().getCamera(), 15.0f, 0.5f);
    } else {
      firstPersonInputHandler = null;
    }
    if (enableKeys) {
      initKeyBindings();
    }

    rootNode.updateRenderState();
    rootNode.updateWorldBound();
    rootNode.updateGeometricState(0.0f, true);
  }
  
  public PhysicsSpace getPhysicsSpace() {
    return physicsSpace;
  }

  public LightState getLightState() {
    return lightState;
  }

  private void initKeyBindings() {
    KeyBindingManager.getKeyBindingManager().set(
        "toggle_pause", KeyInput.KEY_1);
    KeyBindingManager.getKeyBindingManager().set(
        "toggle_wire", KeyInput.KEY_2);
    KeyBindingManager.getKeyBindingManager().set(
        "toggle_lights", KeyInput.KEY_3);
    KeyBindingManager.getKeyBindingManager().set(
        "toggle_bounds", KeyInput.KEY_4);
    KeyBindingManager.getKeyBindingManager().set(
        "toggle_normals", KeyInput.KEY_5);
    KeyBindingManager.getKeyBindingManager().set(
        "camera_out", KeyInput.KEY_6);
    KeyBindingManager.getKeyBindingManager().set(
        "screen_shot", KeyInput.KEY_F1);
    KeyBindingManager.getKeyBindingManager().set(
        "exit", KeyInput.KEY_ESCAPE);
    KeyBindingManager.getKeyBindingManager().set(
        "parallel_projection", KeyInput.KEY_F2);
    KeyBindingManager.getKeyBindingManager().set(
        "toggle_depth", KeyInput.KEY_F3);
    KeyBindingManager.getKeyBindingManager().set(
        "mem_report", KeyInput.KEY_7);
    KeyBindingManager.getKeyBindingManager().set(
        "toggle_mouse", KeyInput.KEY_8);
    KeyBindingManager.getKeyBindingManager().set(
        "show_physics", KeyInput.KEY_9);
    KeyBindingManager.getKeyBindingManager().set(
        "toggle_stats", KeyInput.KEY_0);
    KeyBindingManager.getKeyBindingManager().set(
        "toggle_markers", KeyInput.KEY_F12);
  }

  public void update(float timePerFrame) {
    super.update(timePerFrame);

    if (KeyBindingManager.getKeyBindingManager().isValidCommand(
            "toggle_pause", false)) {
      pause = !pause;
    }
    if (pause) {
      return;
    }
    
    if (firstPersonInputHandler != null) {
      firstPersonInputHandler.update(timePerFrame); 
    }

    rootNode.updateGeometricState(timePerFrame, true);

    if (enableKeys) {
      if (KeyBindingManager.getKeyBindingManager().isValidCommand(
              "toggle_wire", false)) {
        wireFrameState.setEnabled(!wireFrameState.isEnabled());
        rootNode.updateRenderState();
      }
      if (KeyBindingManager.getKeyBindingManager().isValidCommand(
              "toggle_lights", false)) {
        lightState.setEnabled(!lightState.isEnabled());
        rootNode.updateRenderState();
      }
      if (KeyBindingManager.getKeyBindingManager().isValidCommand(
              "toggle_bounds", false)) {
        showBounds = !showBounds;
      }
      if (KeyBindingManager.getKeyBindingManager().isValidCommand(
              "toggle_depth", false)) {
        showDepth = !showDepth;
      }
      if (KeyBindingManager.getKeyBindingManager().isValidCommand(
              "toggle_normals", false)) {
        showNormals = !showNormals;
      }
      if (KeyBindingManager.getKeyBindingManager().isValidCommand("camera_out",
              false)) {
        LOG.info("Camera at: "
                + DisplaySystem.getDisplaySystem().getRenderer().getCamera()
                        .getLocation());
      }
      if (KeyBindingManager.getKeyBindingManager().isValidCommand(
              "screen_shot", false)) {
        DisplaySystem.getDisplaySystem().getRenderer().takeScreenShot(
                "SimpleGameScreenShot");
      }
      if (KeyBindingManager.getKeyBindingManager().isValidCommand(
              "parallel_projection", false)) {
        if (DisplaySystem.getDisplaySystem().getRenderer().getCamera()
                .isParallelProjection()) {
          cameraPerspective();
        } else {
          cameraParallel();
        }
      }
      if (KeyBindingManager.getKeyBindingManager().isValidCommand("mem_report",
              false)) {
        long totMem = Runtime.getRuntime().totalMemory();
        long freeMem = Runtime.getRuntime().freeMemory();
        long maxMem = Runtime.getRuntime().maxMemory();

        LOG.info("|*|*|  Memory Stats  |*|*|");
        LOG.info("Total memory: " + (totMem >> 10) + " kb");
        LOG.info("Free memory: " + (freeMem >> 10) + " kb");
        LOG.info("Max memory: " + (maxMem >> 10) + " kb");
      }
      if (KeyBindingManager.getKeyBindingManager().isValidCommand(
              "toggle_mouse", false)) {
        MouseInput.get().setCursorVisible(!MouseInput.get().isCursorVisible());
        LOG.info("Cursor Visibility set to "
                + MouseInput.get().isCursorVisible());
      }
      if (KeyBindingManager.getKeyBindingManager().isValidCommand(
          "show_physics", false)) {
        showPhysics = !showPhysics;
      }
      if (KeyBindingManager.getKeyBindingManager().isValidCommand(
          "toggle_stats", false)) {
        if (!statisticsCreated) {
          GameStateManager.getInstance().attachChild(
              new StatisticsGameState("stats", 1f, 0.25f, 0.75f, true));
          statisticsCreated = true;
        }
        GameStateManager.getInstance().getChild("stats").setActive(
            !GameStateManager.getInstance().getChild("stats").isActive());
      }
      if (KeyBindingManager.getKeyBindingManager().isValidCommand(
          "toggle_markers", false)) {
        showMarkers = !showMarkers;
      }
      if (KeyBindingManager.getKeyBindingManager()
              .isValidCommand("exit", false)) {
        System.exit(0);
      }
    }
    
    getPhysicsSpace().update(timePerFrame);
  }

  protected void cameraPerspective() {
    DisplaySystem display = DisplaySystem.getDisplaySystem();
    Camera cam = display.getRenderer().getCamera();
    cam.setFrustumPerspective(45.0f, (float) display.getWidth()
            / (float) display.getHeight(), 1, 1000);
    cam.setParallelProjection(false);
    cam.update();
  }

  protected void cameraParallel() {
    DisplaySystem display = DisplaySystem.getDisplaySystem();
    Camera cam = display.getRenderer().getCamera();
    cam.setParallelProjection(true);
    float aspect = (float) display.getWidth() / display.getHeight();
    cam.setFrustum(-100.0f, 1000.0f, -50.0f * aspect, 50.0f * aspect, -50.0f,
            50.0f);
    cam.update();
  }

  public void render(float timePerFrame) {
    super.render(timePerFrame);
    
    DisplaySystem.getDisplaySystem().getRenderer().draw(rootNode);

    if (showBounds) {
      Debugger.drawBounds(rootNode, 
          DisplaySystem.getDisplaySystem().getRenderer(), true);
    }

    if (showNormals) {
      Debugger.drawNormals(rootNode, 
          DisplaySystem.getDisplaySystem().getRenderer());
    }

    if (showDepth) {
      DisplaySystem.getDisplaySystem().getRenderer().renderQueue();
      Debugger.drawBuffer(Texture.RenderToTextureType.Depth, Debugger.NORTHEAST,
          DisplaySystem.getDisplaySystem().getRenderer());
    }
    
    if (showPhysics) {
      PhysicsDebugger.drawPhysics(getPhysicsSpace(), 
          DisplaySystem.getDisplaySystem().getRenderer());
    }
    
    if (showMarkers) {
      markerRootNode.draw(DisplaySystem.getDisplaySystem().getRenderer());
    }
  }

  public void cleanup() {
  }
}